﻿//-----------------------------------------------------------------------------
// <copyright file="ButtonBaseExtensions.cs" company="William E. Kempf">
//     Copyright (c) Peter O'Hanlon.
// </copyright>
//-----------------------------------------------------------------------------

namespace Onyx.Command
{
    using System;
    using System.Windows;
    using System.Windows.Controls.Primitives;
    using System.Windows.Input;

    /// <summary>
    /// Provides attached properties that extend the behavior
    /// of controls that derive from ButtonBase.
    /// </summary>
    public static class ButtonBaseExtensions
    {
        /// <summary>
        /// The Command attached dependency property.
        /// </summary>
        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached(
            "Command",
            typeof(ICommand),
            typeof(ButtonBaseExtensions),
            new PropertyMetadata(null, OnCommandChanged));

        /// <summary>
        /// The CommandParameter attached dependency property.
        /// </summary>
        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.RegisterAttached(
            "CommandParameter",
            typeof(object),
            typeof(ButtonBaseExtensions),
            new PropertyMetadata(null, OnCommandParameterChanged));

        /// <summary>
        /// Maps commands to buttons.
        /// </summary>
        private static readonly CommandToButtonsMap commandToButtonsMap = new CommandToButtonsMap();

        /// <summary>
        /// Gets the command.
        /// </summary>
        /// <param name="button">The button.</param>
        /// <returns>
        /// The command associated with the <paramref name="button"/>.
        /// </returns>
        public static ICommand GetCommand(ButtonBase button)
        {
            return (ICommand)button.GetValue(CommandProperty);
        }

        /// <summary>
        /// Sets the command.
        /// </summary>
        /// <param name="button">The button.</param>
        /// <param name="value">The <see cref="ICommand"/>.</param>
        public static void SetCommand(ButtonBase button, ICommand value)
        {
            button.SetValue(CommandProperty, value);
        }

        /// <summary>
        /// Gets the command parameter.
        /// </summary>
        /// <param name="button">The button.</param>
        /// <returns>
        /// The command parameter.
        /// </returns>
        public static object GetCommandParameter(ButtonBase button)
        {
            return (object)button.GetValue(CommandParameterProperty);
        }

        /// <summary>
        /// Sets the command parameter.
        /// </summary>
        /// <param name="button">The button.</param>
        /// <param name="value">The command parameter.</param>
        public static void SetCommandParameter(ButtonBase button, object value)
        {
            button.SetValue(CommandParameterProperty, value);
        }

        /// <summary>
        /// Called when the Command attached dependency property is changed.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing
        /// the event data.</param>
        private static void OnCommandChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            ButtonBase button = source as ButtonBase;
            if (button == null)
            {
                throw new ArgumentException("You must set the Command attached property on an element that derives from ButtonBase.");
            }

            ICommand oldCommand = e.OldValue as ICommand;
            if (oldCommand != null)
            {
                commandToButtonsMap.RemoveButtonFromMap(button, oldCommand);
                oldCommand.CanExecuteChanged -= OnCommandCanExecuteChanged;
                button.Click -= OnButtonBaseClick;
            }

            ICommand newCommand = e.NewValue as ICommand;
            if (newCommand != null)
            {
                commandToButtonsMap.AddButtonToMap(button, newCommand);
                newCommand.CanExecuteChanged += OnCommandCanExecuteChanged;
                button.Click += OnButtonBaseClick;
            }
        }

        /// <summary>
        /// Called when the command's CanExecute changes.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private static void OnCommandCanExecuteChanged(object sender, EventArgs e)
        {
            ICommand command = sender as ICommand;
            if (command != null && commandToButtonsMap.ContainsCommand(command))
            {
                commandToButtonsMap.ForEachButton(
                    command,
                    button =>
                    {
                        object parameter = ButtonBaseExtensions.GetCommandParameter(button);
                        button.IsEnabled = command.CanExecute(parameter);
                    });
            }
        }

        /// <summary>
        /// Called when the associated button is clicked.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private static void OnButtonBaseClick(object sender, RoutedEventArgs e)
        {
            ButtonBase button = sender as ButtonBase;
            ICommand command = ButtonBaseExtensions.GetCommand(button);
            object parameter = ButtonBaseExtensions.GetCommandParameter(button);
            if (command != null && command.CanExecute(parameter))
            {
                command.Execute(parameter);
            }
        }

        /// <summary>
        /// Called when the CommandParameter attached dependency property is changed.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing
        /// the event data.</param>
        private static void OnCommandParameterChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            ButtonBase button = source as ButtonBase;
            if (button == null)
            {
                throw new ArgumentException("You must set the CommandParameter attached property on an element that derives from ButtonBase.");
            }

            ICommand command = ButtonBaseExtensions.GetCommand(button);
            if (command == null)
            {
                return;
            }

            object parameter = ButtonBaseExtensions.GetCommandParameter(button);
            button.IsEnabled = command.CanExecute(parameter);
        }
    }
}
